package com.study.userserviceprovider.customAspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.*;

@Aspect
public class CustomHystrixCommandAspect {
    ExecutorService executorService = Executors.newFixedThreadPool(10);


    @Pointcut(value = "@annotation(CustomHystrixCommand)")
    public void pointcut() {
    }


    @Around(value = "pointcut()&&@annotation(customHystrixCommand)")
    public Object methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint joinPoint,
                                                     CustomHystrixCommand customHystrixCommand)
            throws InterruptedException, ExecutionException, TimeoutException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        long timeOut = customHystrixCommand.timeOut();

        Future future = executorService.submit(()->{
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return null;
        });

        String fallBack = customHystrixCommand.fallBack();
        try {
             return  future.get(timeOut, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
            future.cancel(true);
            if("".equals(fallBack)){
                throw e;
            }

            return invocationFallBack(joinPoint,fallBack);
        }

    }

    private Object invocationFallBack(ProceedingJoinPoint joinPoint,
                                      String fallBack) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            Class<?>[] parameteTypes = method.getParameterTypes();
            Method fallBackMethod = joinPoint.getTarget().getClass().getMethod(fallBack,parameteTypes);
            fallBackMethod.setAccessible(true);
            return fallBackMethod.invoke(joinPoint.getTarget(),parameteTypes);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

}
